#include <ximera/threading/thread.hpp>
#include <ximera/assert.hpp>
#include <ximera/common/utils.hpp>

#include <boost/bind.hpp>
#include <boost/thread/xtime.hpp>

namespace ximera { namespace threading
{
    Thread::Thread(): _thread(NULL), _running(false), _shouldExit(false)
    {
    }

    Thread::Thread(RunFunction const& function)
        : _thread(NULL), 
          _running(false), 
          _shouldExit(false), 
          _runFunction(function)
    {
    }

    Thread::~Thread()
    {
        stopAndJoin();
    }

    void Thread::start()
    {
        XIMERA_CHECK(!_running, XIMERA_THROW(ThreadException, L"Thread is already running"));

        safe_delete(_thread);
        _thread = new boost::thread(boost::bind(&Thread::internalRun, this));
    }

    void Thread::stop()
    {
        _shouldExit = true;
    }

    void Thread::join()
    {
        if (_thread)
        {
            _thread->join();
            safe_delete(_thread);
        }
    }

    void Thread::stopAndJoin()
    {
        stop();
        join();
    }

    void Thread::runFunction(Thread::RunFunction const& function)
    {
        _runFunction = function;
    }

    void Thread::internalRun()
    {
        try
        {
            if (!_runFunction.empty())
            {
                _running = true;
                _runFunction(boost::bind(&Thread::shouldExit, this));
            }
        }
        catch (Exception&)
        {
            _running = _running;
        }
        catch (...)
        {
            _running = _running;
        }

        _running = false;
    }

    bool Thread::isRunning()
    {
        return _running;
    }

    bool Thread::shouldExit()
    {
        return _shouldExit;
    }

    const int MILLISECONDS_PER_SECOND = 1000;
    const int NANOSECONDS_PER_SECOND = 1000000000;
    const int NANOSECONDS_PER_MILLISECOND = 1000000;

    const int MICROSECONDS_PER_SECOND = 1000000;
    const int NANOSECONDS_PER_MICROSECOND = 1000;

    inline void to_time(int milliseconds, boost::xtime& xt)
    {
        int res = 0;
        res = boost::xtime_get(&xt, boost::TIME_UTC);
        assert(res == boost::TIME_UTC);

        xt.sec += (milliseconds / MILLISECONDS_PER_SECOND);
        xt.nsec += ((milliseconds % MILLISECONDS_PER_SECOND) *
            NANOSECONDS_PER_MILLISECOND);

        if (xt.nsec >= NANOSECONDS_PER_SECOND)
        {
            ++xt.sec;
            xt.nsec -= NANOSECONDS_PER_SECOND;
        }
    }

    void Thread::sleep(UInt32 millisecs)
    {
        boost::xtime time;
        to_time(millisecs, time);

        boost::thread::sleep(time);
    }
}}
